package nodes;

import java.util.ArrayList;
import java.util.Random;

import nodes.typeconst.ConstantStrategy;

import func.Function;

public class TreeManipulator<T> {

	private final double fpr = 0.5;
	private final double ppr = 0.6;
	private final int DEPTH_OF_RANDOM_TREE = 8;
	
	private double probchange;
	private double probswap;
	private Random ran = new Random();
	private ArrayList<Function<T>> func;
	private ConstantStrategy<T> constStrategy;
	
	public TreeManipulator(ArrayList<Function<T>> func, ConstantStrategy<T> strategy) {
		this(0.1, 0.7, func, strategy);
	}
	
	public TreeManipulator(double probchange, double probswap, 
			ArrayList<Function<T>> func, ConstantStrategy<T> strategy) {
		this.probchange = probchange;
		this.probswap = probswap;
		this.func = func;
		this.constStrategy = strategy;
	}
	
	public Node<T> crossover(Node<T> node1, Node<T> node2, boolean top) {
		if(ran.nextDouble() < probswap && !top)
			return node2.copyNode();
		else {
			Node<T> result = node1.copyNode();
			if(node1 instanceof FuncNode && node2 instanceof FuncNode) {
				FuncNode<T> fn1 = (FuncNode<T>) result;
				FuncNode<T> fn2 = (FuncNode<T>) node2;
				
				//ArrayList<Node<T>> children1 = fn1.getChildren();
				ArrayList<Node<T>> children2 = fn2.getChildren();
				
				int i = ran.nextInt(children2.size());
				ArrayList<Node<T>> newchildren = new ArrayList<Node<T>>(3);
				for(Node<T> child : fn1.getChildren())
					newchildren.add(crossover(child, children2.get(i), false));
					
				fn1.setChildren(newchildren);
			}
			
			return result;
		}
	}
	
	public Node<T> mutate(Node<T> node, int pc) {
		if(ran.nextDouble() < probchange)
			return makeRandomTree(pc, DEPTH_OF_RANDOM_TREE);
		else {
			Node<T> result = node.copyNode();
			if(result instanceof FuncNode) {
				FuncNode<T> fn = (FuncNode<T>) result;
				ArrayList<Node<T>> newchildren = new ArrayList<Node<T>>(3);
				//ArrayList<Node<T>> children = fn.getChildren();
				for(Node<T> child : fn.getChildren())
					newchildren.add(mutate(child, pc));
				
				fn.setChildren(newchildren);
				return fn;
			}
			
			return result;
		}
	}
	
	public Node<T> makeRandomTree(int pc, int maxdepth) {
		if(ran.nextDouble() < fpr && maxdepth > 0) {
			int i = ran.nextInt(func.size());
			Function<T> f = func.get(i);
			ArrayList<Node<T>> children = new ArrayList<Node<T>>(3);
			for(int j = 0; j < f.paramCount(); j++)
				children.add(makeRandomTree(pc, maxdepth - 1));
			
			return new FuncNode<T>(f, children);
		}
		else if(ran.nextDouble() < ppr)
			return new ParamNode<T>(ran.nextInt(pc));
		else
			return new ConstNode<T>(constStrategy.getConstant());
	}
}
